---------------Ultima II---------------
A 4am crack                  2022-06-07
---------------------------------------

Name: Ultima II
Version: rev. 3 (*)
Genre: RPG
Year: 1982
Credits: Kenneth W. Arnold, Richard
  Garriott, Keith Zabalaoui, Helen
  Walker, Owen Garriott, Howard Makler,
  Mary Taylor Rollo
Publisher: On-Line Systems
Platform: Apple ][+ or later (48K)
Media: 5.25-inch disk
Sides: 3
OS: DOS 3.3
Previous cracks: none (of this version)

(*) I have found evidence of at least
    three releases. The second one
    makes unspecified changes in the
    ULTIMA II.OBJ file. This third one
    adds a COPYIT file which runs when
    you first create a character, which
    prompts you to make a copy of the
    (unprotected) player master disk.

                   ~

               Chapter 0
 In Which Various Automated Tools Fail
          In Interesting Ways


COPYA
  no errors, but program master boots
  to DOS, fills the screen with inverse
  "R" characters, and reboots

Locksmith Fast Disk Backup
  ditto

EDD 4 bit copy (no sync, no count)
  ditto

Passport
  copies but applies no patches
  copy has same behavior has others

Copy ][+ nibble editor
  no obvious shenanigans
  no track $23

Disk Fixer
  T00-T02 appear to be standard DOS 3.3
  except that commands like LOAD and
  SAVE have been changed to L?AD and
  S?VE.
  Also, on T01,S0F, the sector number
  of the directory VTOC is changed
  from 0 to 1.
  T11 appears as a corrupted disk
  catalog because of the aforementioned
  VTOC change. Copying T11,S01 to
  T11,S00 allows third-party tools to
  perform a CATALOG and load files
  T01,S09 -> startup program is "HELLO"

Why didn't COPYA work?
  Probably a runtime protection check
  looking for... something? Between the
  sectors I guess?

Why didn't Locksmith FDB work?
  ditto

Why didn't my EDD copy work?
  An excellent question... that I can't
  yet answer.

Next steps:

  1. Find the protection check
  2. Disable it
  3. Declare victory (*)

(*) go to the home gym

                   ~

               Chapter 1
           Unlucky In Cards


Since my copy goes down a different
code path than the original, I'm
guessing there is a runtime protection
check somewhere. Disks do not simply
fill the screen with repeating garbage
and reboot unless someone tells them
to.

Firing up my trusty sector editor, I
look for obvious shenanigans but have
no luck. Outside of DOS, there are no
instances of "LDA $C089, X" to turn the
drive motor on. In fact, there are no
other instances of "89 C0" at all. Nor
"E9 C0" (to hit the slot 6 motor
without a slot index). Nor even "8C C0"
(to hit the data latch).

The disk puts the directory VTOC on
T11,S01 and fills T11,S00 with garbage.
Copying sector 1 over sector 0 allows
me to access the files.

[S6,D1=non-working copy]
[S5,D1=work disk]

]PR#5
]CATALOG,S6

C1983 DSR^C#254
073 FREE

*B 033 HELLO
*B 007 II
*B 013 FL
*B 004 WII
*B 012 UL
*B 012 STD SUBS.OBJ
*B 003 UPDATE.OBJ
*B 008 CREATE
*B 003 PLAYER
*B 006 TABLES.903C.OBJ
*B 010 SHAPES
*B 010 MONSTERS!
*B 009 DNGDRAW.OBJ
*B 059 ULTIMA II.OBJ
*B 033 PIC.OUT
*B 033 PIC.TWN
*B 033 PIC.DNG
*B 033 PIC.SPA
*B 033 PIC.MIN
*B 033 PIC.CAS
*B 033 PIC.RUS
 B 004 COPYIT

]BLOAD HELLO
]PAD
A$6000,L$1420
]CALL -151

*6000L

6000-   20 58 FC    JSR   $FC58
6003-   20 80 72    JSR   $7280
6006-   04          ???
6007-   CE CF CD    DEC   $CDCF
600A-   CF          ???
600B-   CE A0 C9    DEC   $C9A0
600E-   AC CF AC    LDY   $ACCF
6011-   C3          ???
6012-   8D 00

This looks like garbage but it's just
embedded ASCII in the code, probably
being read by the subroutine at $7280.

*400<6006.6012M

DNOMON I,O,CM

The "D" is inverse, so it's really a
Ctrl-D. The subroutine would manipulate
the stack pointer to get access to the
"embedded" command, then print the
characters through COUT, which would
trigger a DOS command and execute it as
if you had typed it at the prompt
yourself.

*7280L

7280-   20 E0 72    JSR   $72E0
7283-   4C F8 66    JMP   $66F8

Hmm, that is not what I expected, but
maybe there are more layers and the
sub-sub-routine is what prints the
embedded command through COUT.

*72E0L

72E0-   AD F3 03    LDA   $03F3
72E3-   8D F4 03    STA   $03F4
72E6-   4C F7 72    JMP   $72F7

Clobbering the reset vector like this
will make reset reboot, which is fine
but still has nothing to do with this
embedded command.

*72F7L

72F7-   CE FA 72    DEC   $72FA
72FA-   EF          ???
72FB-   FA          ???
72FC-   72          ???

More embedded ASCII? No! This is a
self-decrypting routine. The first
instruction at $72F7 is changing the
second instruction at $72FA. You can
get away with this on an Apple II
because there is absolutely no pipeline
cache. The opcode at $72FA won't be
read until the previous instruction
completes.

*72FA:EE
*72FAL

72FA-   EE FA 72    INC   $72FA

This, however, will have no effect on
the instruction while it's executing.
(Of course it will change the memory
location in case the program counter
ends up there again in the future.)

So we've decremented and incremented
the same memory location. Fantastic.

                   ~

               Chapter 2
       Are You Watching Closely?


Continuing from the next instruction
at $72FD...

72FD-   AD 1F 73    LDA   $731F
7300-   49 8A       EOR   #$8A
7302-   D0 01       BNE   $7305
7304-   20 8D 1F    JSR   $1F8D
7307-   73          ???

*731F
731F- 60

The branch at $7302 is always taken...
into the middle of the next instruction
listed at $7304. That means the JSR is
fake and will never be called, because
execution continues at $7305.

*7305L

7305-   8D 1F 73    STA   $731F
7308-   18          CLC
7309-   D0 01       BNE   $730C
730B-   4C A0 29    JMP   $29A0

Again with the branch-into-the-middle-
of-the-next-instruction obfuscation.
That JMP target is irrelevant, because
the JMP is never executed.

*730CL

730C-   A0 29       LDY   #$29
730E-   98          TYA
730F-   90 01       BCC   $7312
7311-   20 59 F7    JSR   $F759

Again.

*7312L

7312-   59 F7 72    EOR   $72F7,Y
7315-   99 F7 72    STA   $72F7,Y
7318-   C8          INY
7319-   D0 F3       BNE   $730E
731B-   88          DEY
731C-   30 01       BMI   $731F
731E-   4C 60 E1    JMP   $E160

Again.

*731FL

731F-   60          RTS

OK, I'm going to put NOP instructions
at each of the branch-over-opcode
locations, so I can see the entire
listing at once and figure out what's
going on.

*7304:EA
*730B:EA
*7311:EA
*731E:EA

*72FDL

72FD-   AD 1F 73    LDA   $731F
7300-   49 8A       EOR   #$8A
7302-   D0 01       BNE   $7305
7304-   EA          NOP

; note: this is modifying code later in
; this routine
7305-   8D 1F 73    STA   $731F
7308-   18          CLC
7309-   D0 01       BNE   $730C
730B-   EA          NOP
730C-   A0 29       LDY   #$29
730E-   98          TYA
730F-   90 01       BCC   $7312
7311-   EA          NOP

; decrypt the code at $72F7+$29, which
; is $7320
7312-   59 F7 72    EOR   $72F7,Y
7315-   99 F7 72    STA   $72F7,Y
7318-   C8          INY
7319-   D0 F3       BNE   $730E
731B-   88          DEY
731C-   30 01       BMI   $731F
731E-   EA          NOP

; this was modified earlier
731F-   60          RTS

$60 XOR $8A = $EA, so by the time we
reach this instruction, it's been
changed to a NOP and execution will
continue to... the decrypted code!
Clever.

This decryption routine is self-
contained. If I run it from $7308
(after the RTS-to-NOP modification), I
can see the decrypted code at $7320.

*7308G
(returns to monitor)

And here we go.

                   ~

               Chapter 3
 These Are A Few Of My Favorite Things


I am, like, 99% sure that we have found
the actual copy protection routine.
Otherwise why go to all this trouble to
hide it?

*7320L

7320-   C8          INY

; Y was $FF at the end of the loop that
; decrypted this code, so now it's $00
7321-   8C F4 B7    STY   $B7F4
7324-   8C EC B7    STY   $B7EC

; seek to track 0
7327-   A9 B7       LDA   #$B7
7329-   A0 E8       LDY   #$E8
732B-   20 00 BD    JSR   $BD00

; turn on drive motor (aha! this is why
; I couldn't find it before, because it
; was encrypted)
732E-   BD 89 C0    LDA   $C089,X

; death counter maybe?
7331-   A9 05       LDA   #$05
7333-   8D 00 BB    STA   $BB00
7336-   20 87 73    JSR   $7387

*7387L

7387-   A9 1C       LDA   #$1C
7389-   8D 02 BB    STA   $BB02

; reset data latch
738C-   BD 8E C0    LDA   $C08E,X

; find $D5 nibble
738F-   BD 8C C0    LDA   $C08C,X
7392-   10 FB       BPL   $738F
7394-   C9 D5       CMP   #$D5
7396-   EA          NOP
7397-   EA          NOP
7398-   F0 0F       BEQ   $73A9

; $BB01/$BB02 is a failsafe counter for
; finding the nibble. (Note that $BB01
; is uninitialized, but it's the low
; byte of the 2-byte word so it doesn't
; matter much.)
739A-   CE 01 BB    DEC   $BB01
739D-   D0 F0       BNE   $738F
739F-   CE 02 BB    DEC   $BB02
73A2-   D0 EB       BNE   $738F

; if we can't even find $D5, pop the
; stack (because we JSR'd here) and
; jump to what I assume is The Badlands
73A4-   68          PLA
73A5-   68          PLA
73A6-   4C 5A 73    JMP   $735A

; execution continues here (from $7398)
; once we find the $D5 nibble
73A9-   BD 8C C0    LDA   $C08C,X
73AC-   10 FB       BPL   $73A9

; now looking for $AA
73AE-   C9 AA       CMP   #$AA
73B0-   D0 E2       BNE   $7394
73B2-   48          PHA
73B3-   68          PLA
73B4-   BD 8C C0    LDA   $C08C,X
73B7-   10 FB       BPL   $73B4

; then $96
73B9-   C9 96       CMP   #$96
73BB-   D0 F1       BNE   $73AE
73BD-   A0 05       LDY   #$05
73BF-   20 EC 73    JSR   $73EC

*73ECL

; a compact little subroutine that will
; skip over a given number of nibbles
; (Y is set in the caller)
73EC-   BD 8C C0    LDA   $C08C,X
73EF-   10 FB       BPL   $73EC

; just burning cycles so we don't
; accidentally read the same nibble
; twice
73F1-   48          PHA
73F2-   68          PLA
73F3-   88          DEY
73F4-   D0 F6       BNE   $73EC

; note that the last nibble read is
; still in A when we return
73F6-   60          RTS

Continuing from $73C2...

; the 5th nibble needs to be $AA (this
; will be part of the sector number in
; the address field)
73C2-   C9 AA       CMP   #$AA
73C4-   D0 C9       BNE   $738F
73C6-   BD 8C C0    LDA   $C08C,X
73C9-   10 FB       BPL   $73C6

; the 6th nibble also needs to be $AA
; (this will be the other half of the
; sector number in the 4-and-4-encoded
; address field, so we're looking for
; sector 0)
73CB-   C9 AA       CMP   #$AA
73CD-   D0 C0       BNE   $738F
73CF-   48          PHA
73D0-   68          PLA
73D1-   BD 8C C0    LDA   $C08C,X
73D4-   10 FB       BPL   $73D1

; unconditionally (without counting)
; look for the next $D5 nibble, which
; should be the start of the data
; prologue for sector 0
73D6-   C9 D5       CMP   #$D5
73D8-   D0 F7       BNE   $73D1
73DA-   EA          NOP
73DB-   BD 8C C0    LDA   $C08C,X
73DE-   10 FB       BPL   $73DB

; $AA nibble, also part of the data
; prologue
73E0-   C9 AA       CMP   #$AA
73E2-   D0 F2       BNE   $73D6
73E4-   EA          NOP

; skip $100 nibbles
73E5-   A0 00       LDY   #$00
73E7-   20 EC 73    JSR   $73EC

; skip $5B more nibbles
73EA-   A0 5B       LDY   #$5B

; note: this falls through to the same
; subroutine entry point we called
; earlier, which is nice
73EC-   BD 8C C0    LDA   $C08C,X
73EF-   10 FB       BPL   $73EC
73F1-   48          PHA
73F2-   68          PLA
73F3-   88          DEY
73F4-   D0 F6       BNE   $73EC
73F6-   60          RTS

Routines that fall through to other
routines that they previously called as
subroutines are my favorite thing about
assembly language. (My least favorite
thing is everything else.)

So far, we've seeked to track 0, found
sector 0, and skipped basically all of
it.

Continuing from $7339...

; this will always branch, because the
; last thing that happened was that Y
; was decremented to 0, which is still
; technically positive as far as the
; 6502 is concerned
7339-   10 01       BPL   $733C
733B-   20 C8 C0    JSR   $C0C8

!@#$% Grr.

*733CL

; burn some number of cycles
; (Y starts at 0 because that's where
; it ended in the previous subroutine)
733C-   C8          INY
733D-   C0 4B       CPY   #$4B
733F-   90 FB       BCC   $733C
7341-   F0 01       BEQ   $7344
7343-   4C BD 8C    JMP   $8CBD

I'm so tired.

*7344L

; on first glance this looks like a
; regular LDA/BPL loop to read a nibble
; EXECPT it's branching forwards, not
; backwards
7344-   BD 8C C0    LDA   $C08C,X
7347-   10 0A       BPL   $7353

; now comparing the RAW DATA LATCH READ
; at $7344
7349-   C9 C9       CMP   #$C9
734B-   D0 0D       BNE   $735A

; success path falls through here --
; turn off drive motor and exit via
; $72F7 (more on this in a moment)
734D-   BD 88 C0    LDA   $C088,X
7350-   4C F7 72    JMP   $72F7

; execution continues here (from $7347)
; if the raw data latch read is NOT the
; expected $C9 value
7353-   EA          NOP
7354-   EA          NOP

; apparently we get one more chance to
; find the right nibble, because after
; burning some cycles with the NOPs, if
; the data latch has gone high (meaning
; a full nibble value is available), we
; branch back to the CMP to see if it
; now matches the expected $C9 nibble
7355-   BD 8C C0    LDA   $C08C,X
7358-   30 EF       BMI   $7349

; decrement Death Counter and try again
735A-   CE 00 BB    DEC   $BB00
735D-   D0 D7       BNE   $7336

; out of chances -- copy the final code
; to the bottom of the stack page and
; jump there (we ain't comin' back)
735F-   A2 1F       LDX   #$1F
7361-   9A          TXS
7362-   BD 6E 73    LDA   $736E,X
7365-   9D 00 01    STA   $0100,X
7368-   CA          DEX
7369-   10 F7       BPL   $7362
736B-   4C 00 01    JMP   $0100

; (executes from $100)
; wipe main memory and reboot
736E-   A9 12       LDA   #$12
7370-   9D 00 02    STA   $0200,X
7373-   E8          INX
7374-   D0 FA       BNE   $7370
7376-   EE 04 01    INC   $0104
7379-   AC 04 01    LDY   $0104
737C-   C0 C0       CPY   #$C0
737E-   F0 F6       BEQ   $7376
7380-   C0 01       CPY   #$01
7382-   D0 EC       BNE   $7370
7384-   6C FC FF    JMP   ($FFFC)

So that's it. At one of two exact times
after sector 0, there needs to be a $C9
nibble. If we find it, we exit through
$72F7; otherwise we wipe memory and
reboot.

Wait, why are we exiting through $72F7?
Isn't that where we started? Surely we
are not stuck in an infinite loop.

Indeed we are not. $72F7 will (again)
decrement and re-increment the opcode
at $72FA, then do this again:

72FD-   AD 1F 73    LDA   $731F
7300-   49 8A       EOR   #$8A
7302-   D0 01       BNE   $7305
7304-   EA          NOP
7305-   8D 1F 73    STA   $731F

Previously, that changes the opcode at
$731F from "RTS" to "NOP", so execution
fell through to the protection code at
$7320. But now it does the opposite: it
changes the "NOP" back to "RTS"! And
since the rest of the decryption was
also just XOR, it will end up re-
encrypting the protection code in
memory, keeping it relatively safe from
prying eyes. (I am the prying eyes.)
Then it hits $731F again, which is now
an "RTS", so it gracefully exits to the
caller. Very very smooth.

                   ~

               Chapter 4
     Now You See It, Now You Don't


I understand WHAT the protection check
is doing, but I don't understand WHY.
A sector copy of this original disk
will not copy the nibbles between the
sectors, which explains why COPYA and
similar fast copiers failed. But what
is so special about the nibbles after
track 0, sector 0? Why can't a bit
copier like EDD copy it?

Using the Copy ][+ nibble editor, we
can look at the raw nibbles on track 0,
including the crucial $C9 nibble. Here
is track 0, starting near the end of
the data field of sector 0. (I've
converted the inverse characters into
"+" signs to indicate where Copy ][+
found one or more timing bits after a
nibble.)

                 --v--

   COPY ][ PLUS BIT COPY PROGRAM 8.4
(C) 1982-9 CENTRAL POINT SOFTWARE, INC.
----------------------------------------

TRACK: 00  START: 2029  LENGTH: 188E

2188: 96 96 96 96 96 96 96 96   VIEW
2190: 96 96 96 96 96 96 96 EA
2198: EC 9A DE AA EB EA+FF+FF+
21A0: FF+FF+BA+FF+FF+FF+FF+FF+
21A8: FF+FF+FF+FE+C9+FF+FF+FF+ <-21AC
21B0: FF+FF+FF+AC+FF+99+FF+99+
21B8: E4 E4 E4+FF+A4 FF+9C FF+
21C0: FF+FF+FF+FF+92 92 92 FF+
21C8: 96 FF+FF+FF+9D F2+FF+FF+

                 --^--

The exact nibble offsets are not
important (they'll change if we re-read
the disk), but this is what we have at
offset $219A:

DE AA EB     ; data epilogue
EA
FF FF FF FF
BA
FF FF FF FF FF FF FF FF
FE
C9
...

Seems normal enough. But let's read the
same track again...

                 --v--

26F0: 96 96 96 96 96 96 96 96   VIEW
26F8: 96 96 96 96 96 EA EC 9A
2700: DE AA EB EA+D9+FF+E5 E5+
2708: FF+FF+FF+FF+FF+9F E7 F9
2710: FC C9 C9 C9+FF+FF+FF+D6  <-2712
2718: DB+FF+A4+FF+FF+FF+AD 94
2720: FF+FF+A9 A9 CB+FF+FF+FF+
2728: FF+FF+FF+EA FF+FF+FF+FF+
2730: FF+BA+FF+FF+FF+FF+FF+FF+

                 --^--

Again, the exact nibble offsets are not
important. But look at the "data" after
sector 0 (starting at offset $2700):

DE AA EB     ; data epilogue
EA
D9
FF
E5
E5
FF FF FF FF FF
9F
E7
F9
FC
C9
C9
C9

In fact, this region of the original
disk will look different every time we
read it. How? I'm glad you asked.

There's only one thing you can put on a
disk that will change every time you
read it: nothing. And by "nothing," I
mean "a long sequence of zero bits."
And that's what is on the original disk
before (and, as it turns out, after)
the $C9 nibble: nothing.

A bit of background. When we say a
"zero bit," we really mean "the lack of
a magnetic state change." The Disk II
drive isn't digital; it's analog. If it
doesn't see a state change in a certain
period of time, it calls that a "0". If
it does see a change, it calls that a
"1". But the drive can only tolerate a
lack of state changes for so long --
about as long as it takes for two bits
to go by.

Fun fact(*): this is why disks use
nibbles as an intermediate on-disk
format in the first place. No valid
nibble contains more than two zero bits
consecutively, when written from most-
significant to least-significant bit.

(*) not guaranteed, actual fun may vary

So what happens when a drive doesn't
see a state change after the equivalent
of two consecutive zero bits? The drive
thinks the disk is weak, and the MC3470
chip inside the drive starts increasing
the amplification to try to compensate,
looking for a valid signal.  But there
is no signal. There is no data. There
is just a yawning abyss of nothingness.
Eventually, the drive gets desperate
and amplifies so much that it starts
returning random bits based on ambient
noise from the disk motor and the
magnetism of the Earth.

Seriously.

It's trivial to write zero bits to a
disk; just write a #$00 nibble to write
8 zero bits -- like any other 8-bit
nibble. You can write whatever you want
to a disk; it doesn't need to be what
DOS would consider a "valid" nibble.
But when you read that nibble back, the
drive can't handle 8 zero bits in a
row, so it will actually return some
random bits. Which is why no one does
that.

Returning random bits doesn't sound
very useful for a storage device, but
it's exactly what the developer wanted,
and that's exactly what this copy
protection scheme depends on. Here's
why:

Bit copiers can't duplicate a long
sequence of zero bits.

Why? Because that's not what they see.
What they see is some random bits --
the real zero bits interspersed with
phantom "1" bits. So that's what they
write to the target disk. Whatever
randomness they get when they read the
original disk will essentially get
"frozen" onto the copy.

But wait, it gets worse.

                   ~

               Chapter 5
  In Which We Jump Out Of The System,
           Then Jump Back In


To understand exactly what is on the
original disk, and why this copy
protection works, we need to jump
outside the system and use modern tools
that can see the disk at a different
level.

With an Applesauce hardware controller,
a modified drive, and the associated
Applesauce.app software, we can get a
flux-level read of exactly what is on
the original disk.

Starting immediately after the data
epilogue of sector 0:

11101010     ; $EA nibble
00000000     ; long sequence of 0 bits
...          ; (102 "bits" long)
1111111100   ; $FF/10 (sync nibble)
1111111100   ; $FF/10 (sync nibble)
111111100    ; $FE/9 (sync nibble)
11001001     ; $C9 nibble
00000000...  ; long sequence of 0 bits

Those 102 bits after the $EA nibble can
randomly resolve into anything, which
will then be interpreted as nibbles.
The sync nibbles might resynchronize
the stream, so that when it reads the
data latch at the crucial moment, the
$C9 nibble is there. Or they might not.
There aren't enough sync nibbles to
guarantee resynchronization. (See
p. 3-8 of "Beneath Apple DOS" for a
more detailed explanation of why you
need at least four $FF/10 nibbles to
resynchronize in all cases.) With the
element of randomness, it's possible
that those 102 zero bits could resolve
to a bitstream where the nibbles are
still out of phase at the crucial
moment when it checks for the $C9
nibble. In that case, it will decrement
the Death Counter in $BB00 and start
over. It has 5 chances to get it right.

Could this actually happen? I have the
original disk and real hardware, and I
know how to get the protection code
decrypted in memory, so let's find out.

; return on success (instead of exiting
; through $72F7 to re-encrypt)
*7350:60

; return on failure (instead of jumping
; to The Badlands)
*735F:60

And now a driver that calls this
patched protection code in memory:

*300:A0 FF 20 20 73 AE 00 BB FE 41 03
     CE 40 03 D0 F0 60

*300L

0300-   A0 FF       LDY   #$FF
0302-   20 20 73    JSR   $7320
0305-   AE 00 BB    LDX   $BB00
0308-   FE 41 03    INC   $0341,X
030B-   CE 40 03    DEC   $0340
030E-   D0 F0       BNE   $0300
0310-   60          RTS

*340:0 0 0 0 0 0 0

[S6,D1=original disk]

*300G
...read read read...

*341.346

0341- 00 02 08 0C 29 C1

That's a nice distribution. It always
succeeded eventually -- about 90% of
the time on the first or second try.
But this test clearly shows that the
Death Counter is a necessary component
of this copy protection. Two times out
of 256, it only succeeded on the fifth
and final try!

I ran the same test a few more times
and got a similar distribution. But on
the fourth run, $0341 was non-zero,
meaning that the death counter had
decremented to 0 and the protection had
failed.

About one time in a thousand, this
protection check will fail on an
original disk.

                   ~

               Chapter 6
  May The Odds Be Ever In Your Favor


So what happens on a bit copy? The
short answer is... it depends. It might
work! It's random! Maybe it will
interpret the random bitstream as a
sequence of nibbles that is just the
right length AND just happens to
synchronize to the $C9 nibble, in which
case the copy will pass the protection
check.

The long answer is... the deck is
stacked against you.

In the general case, bit copiers are
trying to do the impossible, and they
succeed a surprising amount of the
time. On a disk like this, most parts
of each track are highly structured.
There are sectors. There are address
fields and data fields, each with
prologues and epilogues and internal
checksums. By design, you can verify
that you read each of these things
properly.

But the bits between the sectors are
the "wild west," with little to no
structure to fall back on. Compounding
their troubles, bit copiers can not
tell the difference between 1 and 2
zero bits after a nibble. The disk is
just too fast and the CPU is too slow;
there's barely enough time to determine
that there's a single zero bit (and
even that routine has limitations that
can be exploited).

Sync nibbles ($FF/10) are generally OK
because they can assume that those are
being used as sync nibbles. But this is
an assumption, not anything they can
verify in real time, and after 4 sync
nibbles in a row, even advanced bit
copiers like EDD will start writing out
$FF/9 instead, unless you tell them
otherwise. (Adventure International
uses this assumption against copiers by
having a long field of $FF/10 nibbles
and noticing that they get rewritten as
$FF/9 nibbles. See 4am crack no. 1563
"The Kingdom of Facts" for details.)

Anyway, bit copiers have a fundamental
problem: they can't tell exactly what's
on the disk, because the disk is too
fast and the CPU is too slow. So they
compensate, guess, and reconstruct.
They build the most probable bitstream
based on the nibble stream at any given
point on the track. But they do not,
and can not, "duplicate" the bitstream.
Every copy is a reconstruction.

When you read complete garbage that
changes every time it comes around,
then try to reconstruct that into some
sort of reasonable bitstream that makes
sense as a nibble stream, you are going
to fail because there aren't really any
nibbles. There is just the yawning
abyss.

Using the same Applesauce software, I
imaged my non-working copy and examined
the problematic region after track 0,
sector 0. Keep in mind that each copy
would look slightly different, due to
how it reads the random garbage. But
even a single copy is instructive.

111010100    ; $EA/9
110000000    ; $C0/9
101110010    ; $B9/9
100111000    ; $9C/9
110100100    ; $D2/9
100000100    ; $82/9
10100111     ; $A7
10100111     ; $A7
101001010    ; $A5/9
100000000    ; $80/9
101010000    ; $A8/9
10111111     ; $BF
11001111     ; $CF
11110011     ; $F3
11111001     ; $F9
10010010     ; $92
100001100    ; $86/9
111010010    ; $E9

This is EXTREMELY interesting. At first
glance, it looks nothing like the
original disk, because some of those
102 0 bits turned into 1 bits. Fine, we
knew that would happen. But more
importantly, there's no $C9 nibble.

Or is there?

If you take the $BF nibble and ignore
the first two bits, you get this:

  111111
11001111
11110011
11111001
10010010

Here, let me rewrite that for you:

1111111100   ; $FF/10
1111111100   ; $FF/10
111111100    ; $FE/9
110010010    ; $C9/9

So there is a $C9 nibble, but it's out
of phase, and the sync field isn't long
enough to resync the data latch to see
it. But more importantly, IT'S IN THE
WRONG PLACE. Count the bits. Between
the $EA nibble and the first $FF/10
nibble, the original disk has 102 bits.
This copy only has 91 bits. By the time
we try to read the crucial $C9 nibble,
we're long past it.

Finally, because the rest of the abyss
has been turned into "real" nibbles,
re-reading the track will not change
the result. The bitstream is too short,
the crucial nibble is out of phase,
there's no longer any meaningful
randomness, and you're all out of luck.

The original disk might fail 0.1% of
the time, but this copy will fail 100%
of the time.

                   ~

               Chapter 7
           Back To The Crack


Searching the disk for the bytes "20 E0
72" (JSR $72E0, the entry point of the
protection routine), I find one match
on track $03.

; change "JSR" to "BIT"
T03,S0C,$84: 20 -> 2C

The code immediately following jumps to
$66F8, which (finally!) prints the
embedded ASCII command to COUT and
returns to the caller. There do not
appear to be any other side effects or
delayed anti-tamper checks, although I
did notice that $7280 is called several
more times. But every caller routes
through $7280, never $72E0, so this
one-byte patch is sufficient to bypass
all the protection checks.

                   ~

               Epilogue


Out of curiosity, I found a scan of an
old EDD program list, which gives brief
descriptions of programs that require
non-default parameters. Here is what
they suggest to make a protected
backup:

                 --v--

       drive speed critical
       recopy t0 until boots
       t0 parm normal or bitcopy
         and/or autonc or manualnc

                 --^--

"recopy t0 until boots"

Now you know why.

Quod erat liberandum.

---------------------------------------
A 4am crack                    No. 2847
------------------EOF------------------
